package ru.niir.goodfil.ui;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.richfaces.event.UploadEvent;
import org.richfaces.model.UploadItem;
import ru.niir.goodfil.audit.util.Assert;
import ru.niir.goodfil.db.*;
import ru.niir.goodfil.exceptions.CreateFailureException;
import ru.niir.goodfil.exceptions.ValidationException;
import ru.niir.goodfil.utils.PriceInfo;
import ru.niir.goodfil.utils.validation.CreateCheckResult;
import ru.niir.goodfil.utils.validation.ValidationError;
import ru.niir.goodfil.utils.validation.ValidationResult;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;

import static org.apache.commons.lang.StringUtils.isBlank;
import static ru.niir.goodfil.ui.Utils.setReservationDate;

public class ClientImportExcelFileOrderBean extends PageBean {
    private static final Logger logger = Logger.getLogger(ClientImportExcelFileOrderBean.class);
    private File file;

    public void listener(UploadEvent event) throws Exception {
        UploadItem item = event.getUploadItem();
        this.file = item.getFile();
    }

    public void processUpload() throws IOException {
        Client client = getSecurityBean().getCurrentClient();
        parseOrderFile(file, client);
    }

    public void doCancelAbsentPositions() {
        restsProblems = new ArrayList<Map<String, String>>();
    }

    public void doRemoveAbsentPositions() {
        System.out.println("doRemoveAbsentPositions");

        restsProblems = new ArrayList<Map<String, String>>();
        Client client = getSecurityBean().getCurrentClient();

        //
        //  Проверяем загруженную из файла накладную
        //
        //  Нужно проверить, что:
        //      - такой товар существует
        //      - указанное количество товара доступно
        for (ImportOrderBean.Item item : items.values()) {
            Good good = gf().getGoodByCode(item.goodCode);
            if (good == null) {
                facesError("В ИНТЕРНЕТ-системе отсутствует товар с кодом " + item.goodCode);
            } else {
                if (good.isDeleted())
                    facesError("Товар с кодом " + item.goodCode + " находится в списке удаленных");
                if (good.getC1CodeIsMissing())
                    facesError("Товар с кодом " + item.goodCode + " не может быть передан в 1С");

                if (item.count > (good.getCount() - good.getReservedCount())) {
                    item.count = good.getCount() - good.getReservedCount();
                    if (item.count < 0) item.count = 0;
                }
            }
        }

        if (hasFacesErrors()) return;

        //  Теперь все ок, можно создавать заказ
        Order order = new Order();
        setReservationDate(order, gf());
        order.setClientId(client.getId());
        order.setManagerId(client.getManagerId());

        client.getDeliveryInfoExt().copyTo(order.getDeliveryInfoExt());
        Calendar deliveryDate = Calendar.getInstance();
        deliveryDate.add(Calendar.DAY_OF_MONTH, order.getDeliveryInfoExt().getPeriod());
        order.setDeliveryDate(deliveryDate.getTime());

        gf().createIfNotExists("deliveryTimeSpan", 7);
        int deliveryTimeSpan = gf().getInt("deliveryTimeSpan");
        order.setDeliveryTypeId(client.getDeliveryTypeId());
        order.setDeliveryAddress(client.getAddressDlv());
        order.setDeliveryTimeSpan(deliveryTimeSpan);
        order.setDeliverySum(0);

        List<OrderItem> orderItems = new ArrayList<OrderItem>();
        for (ImportOrderBean.Item item : items.values()) {
            if (item.count <= 0) continue;

            Good good = gf().getGoodByCode(item.goodCode);
            PriceInfo pi = gf().getPrice2(good, client);

            logger.debug("Price for good " + good.getCode() + ": " + pi);

            OrderItem orderItem = new OrderItem();
            orderItem.setGood(good);
            orderItem.setGoodId(good.getId());
            orderItem.setCount(item.count);
            orderItem.setStartPrice(pi.getPrice());
            orderItem.setPersonalPrice(pi.isPersonalPrice());
            if (item.price > pi.getPrice())
                orderItem.setManualPrice(item.price);

            orderItems.add(orderItem);
        }

        for (OrderItem orderItem : orderItems) {
            if (orderItem.getStartPrice() == 0)
                facesError(orderItem.getGood().getCode() + ": неверная цена в прайсе");
        }

        if (hasFacesErrors()) return;

        this.orderInfo = new OrderInfo(order, orderItems);
    }

    //  Удалять отсутствующие позиции
    private boolean removeAbsentPositions;

    public boolean isRemoveAbsentPositions() {
        return removeAbsentPositions;
    }

    public void setRemoveAbsentPositions(boolean removeAbsentPositions) {
        this.removeAbsentPositions = removeAbsentPositions;
    }

    //  Сообщения о нехватке остатков для данного заказа
    private List<Map<String, String>> restsProblems = new ArrayList<Map<String, String>>();

    public List<Map<String, String>> getRestsProblems() {
        Collections.sort(restsProblems, new Comparator<Map<String, String>>() {
            public int compare(Map<String, String> m1, Map<String, String> m2) {
                return m1.get("code").compareTo(m2.get("code"));
            }
        });
        return restsProblems;
    }

    private Map<String, ImportOrderBean.Item> items = new HashMap<String, ImportOrderBean.Item>();

    private void parseOrderFile(File uploadedFile, Client client) throws IOException {
        restsProblems = new ArrayList<Map<String, String>>();

        FileInputStream inputStream = new FileInputStream(uploadedFile);
        items = new HashMap<String, ImportOrderBean.Item>();

        //
        //  Загружаем данные из файла и проверяем формат значений
        //
        Workbook wb = new HSSFWorkbook(inputStream);
        Sheet sheet1 = wb.getSheetAt(0);

        //  Проверяем, что лист не пустой
        try {
            String s1 = sheet1.getRow(1).getCell(0).getStringCellValue();
            if (isBlank(s1)) throw new NullPointerException();
        } catch (NullPointerException e) {
            facesError("Неверный формат файла");
            return;
        }

        //
        //  Определяем формат файла
        //
        int FIRST_ROW = -1;
        int COL_CODE = -1;
        int COL_COUNT = -1;
        int COL_PRICE = -1;

        try {
            if (sheet1.getRow(0).getCell(0).getStringCellValue().trim().equals("ОСТАТКИ *")) {
                logger.info("Format: new");
                FIRST_ROW = 6;
                COL_CODE = 1;
                COL_COUNT = 6;
            } else {
                logger.info("Format: old");
                FIRST_ROW = 1;
                COL_CODE = 0;
                COL_COUNT = 1;
                COL_PRICE = 2;
            }
        } catch (Exception e) {
            logger.info("Format exception: old");
            FIRST_ROW = 1;
            COL_CODE = 0;
            COL_COUNT = 1;
            COL_PRICE = 2;
        }

        //
        //  Проверяем, что нет незаполненных ячеек ---------------------------------------------------------------------
        //

        for (int i = FIRST_ROW; i <= sheet1.getLastRowNum(); i++) {
            Row row = sheet1.getRow(i);
            if (row == null) facesError("Пустая строка: " + i);

            Cell cell0 = row.getCell(COL_CODE);
            Cell cell1 = row.getCell(COL_COUNT);

            if (cell0 == null) facesError("Пустая ячейка в первой колонке строки #" + (i + 1));
            if (cell1 == null) facesError("Пустая ячейка во второй колонке строки #" + (i + 1));
        }

        if (hasFacesErrors()) return;

        //  ------------------------------------------------------------------------------------------------------------

        for (int i = FIRST_ROW; i <= sheet1.getLastRowNum(); i++) {
            Row row = sheet1.getRow(i);

            Cell cell0 = row.getCell(COL_CODE);
            Cell cell1 = row.getCell(COL_COUNT);
            Cell cell2 = row.getCell(COL_PRICE);

            String goodCode = null;
            int count = 0;
            float price = 0;

            try {
                if (cell0.getCellType() == Cell.CELL_TYPE_STRING) {
                    goodCode = cell0.getStringCellValue();
                    goodCode = GoodfilUtils.normalizeGoodCode(goodCode);

                } else {
                    facesError("Все ячейки первой колонки (\"Код\") должны иметь строковый тип");
                }

                if (cell1.getCellType() == Cell.CELL_TYPE_STRING) {
                    count = Integer.parseInt(cell1.getStringCellValue());
                } else if (cell1.getCellType() == Cell.CELL_TYPE_NUMERIC) {
                    count = (int) cell1.getNumericCellValue();
                    if (count == 0) continue;
                } else {
                    facesError("Все ячейки второй колонки (\"Количество\") должны иметь строковый или числовой тип");
                    return;
                }

                if (cell2 != null) {
                    if (cell2.getCellType() == Cell.CELL_TYPE_STRING) {
                        price = Float.parseFloat(cell2.getStringCellValue());
                    } else if (cell2.getCellType() == Cell.CELL_TYPE_NUMERIC) {
                        price = (float) cell2.getNumericCellValue();
                    } else {
                        facesError("Все ячейки третьей колонки (\"Цена\") должны иметь строковый или числовой тип");
                        return;
                    }
                }

                if (StringUtils.isBlank(goodCode)) {
                    facesError("Строка " + String.valueOf(i + 1) + ": неверное значение ячейки \"Код\"");
                    return;
                }
                if (count <= 0) {
                    facesError("Строка " + String.valueOf(i + 1) + ": неверное значение ячейки \"Количество\"");
                    return;
                }
                if (price < 0) {
                    facesError("Строка " + String.valueOf(i + 1) + ": неверное значение ячейки \"Цена\"");
                    return;
                }

                if (!items.containsKey(goodCode)) {
                    items.put(goodCode, new ImportOrderBean.Item(goodCode, count, price));
                } else {
                    ImportOrderBean.Item item = items.get(goodCode);
                    if (item.price != price) {
                        facesError("Для товара " + goodCode + " указана разная стоимость в разных строках");
                        return;
                    }
                    item.count += count;
                }
            } catch (NumberFormatException nfe) {
                facesError("Ошибка в строчке " + String.valueOf(i + 1));
            }
        }

        //
        //  Проверяем загруженную из файла накладную
        //
        //  Нужно проверить, что:
        //      - такой товар существует
        //      - указанное количество товара доступно
        for (ImportOrderBean.Item item : items.values()) {
            Good good = gf().getGoodByCode(item.goodCode);
            if (good == null) {
                facesError("В ИНТЕРНЕТ-системе отсутствует товар с кодом " + item.goodCode);
            } else {
                if (good.isDeleted())
                    facesError("Товар с кодом " + item.goodCode + " находится в списке удаленных");
                if (good.getC1CodeIsMissing())
                    facesError("Товар с кодом " + item.goodCode + " не может быть передан в 1С");

                if (item.count > (good.getCount() - good.getReservedCount())) {
                    Map<String, String> restProblem = new HashMap<String, String>();
                    restProblem.put("code", good.getCode());
                    restProblem.put("requestedCount", String.valueOf(item.count));
                    restProblem.put("count", String.valueOf(good.getCount() - good.getReservedCount()));
                    restsProblems.add(restProblem);

                    if (removeAbsentPositions) {   //  Изменяем позицию заказа
                        item.count = good.getCount() - good.getReservedCount();
                        if (item.count < 0) item.count = 0;
                    }
                }
            }
        }

        if (restsProblems.size() > 0 && !removeAbsentPositions)
            facesError("Заказ невозможно провести из-за нехватке остатков по некоторым позициям (см. ниже)");

        if (hasFacesErrors()) return;

        //  Теперь все ок, можно создавать заказ
        Order order = new Order();
        setReservationDate(order, gf());
        order.setClientId(client.getId());
        order.setManagerId(client.getManagerId());

        client.getDeliveryInfoExt().copyTo(order.getDeliveryInfoExt());
        Calendar deliveryDate = Calendar.getInstance();
        deliveryDate.add(Calendar.DAY_OF_MONTH, order.getDeliveryInfoExt().getPeriod());
        order.setDeliveryDate(deliveryDate.getTime());

        gf().createIfNotExists("deliveryTimeSpan", 7);
        int deliveryTimeSpan = gf().getInt("deliveryTimeSpan");
        order.setDeliveryTypeId(client.getDeliveryTypeId());
        order.setDeliveryAddress(client.getAddressDlv());
        order.setDeliveryTimeSpan(deliveryTimeSpan);
        order.setDeliverySum(0);

        List<OrderItem> orderItems = new ArrayList<OrderItem>();
        for (ImportOrderBean.Item item : items.values()) {
            if (item.count <= 0) continue;

            Good good = gf().getGoodByCode(item.goodCode);
            PriceInfo pi = gf().getPrice2(good, client);

            logger.debug("Price for good " + good.getCode() + ": " + pi);

            OrderItem orderItem = new OrderItem();
            orderItem.setGood(good);
            orderItem.setGoodId(good.getId());
            orderItem.setCount(item.count);
            orderItem.setStartPrice(pi.getPrice());
            orderItem.setPersonalPrice(pi.isPersonalPrice());
            if (item.price > pi.getPrice())
                orderItem.setManualPrice(item.price);

            orderItems.add(orderItem);
        }

        for (OrderItem orderItem : orderItems) {
            if (orderItem.getStartPrice() == 0)
                facesError(orderItem.getGood().getCode() + ": неверная цена в прайсе");
        }

        if (hasFacesErrors()) return;

        this.orderInfo = new OrderInfo(order, orderItems);

    }

    private OrderInfo orderInfo;

    public boolean isOrderAvailable() {
        return orderInfo != null;
    }

    /**
     * Получить файл-заготовку для набора заказа.
     */
    public void doGetOrderFile() {
        RestsExportBean restsExportBean = (RestsExportBean) getBean("RestsExportBean");

        Long clientId = getSecurityBean().getClientId();
        restsExportBean.setClientId(clientId);
        restsExportBean.doExport();
    }

    public void cancelOrder() {
        this.orderInfo = null;
        this.file = null;
    }

    public void saveOrder() {
        Assert.notNull(orderInfo);
        Assert.notNull(orderInfo.getOrder());
        Assert.notNull(orderInfo.getOrderItems());
        Assert.isTrue(orderInfo.getOrderItems().size() > 0);

        Order order = orderInfo.getOrder();
        List<OrderItem> orderItems = orderInfo.getOrderItems();

        try {
            gf().saveOrder(order);
        } catch (ValidationException ex) {
            logger.error(ex);
            ex.printStackTrace();
            ValidationResult validationResult = ex.getResult();
            for (ValidationError error : validationResult.getErrors()) {
                facesError(error.toHumanString());
            }
        } catch (CreateFailureException ex) {
            logger.error(ex);
            ex.printStackTrace();
            CreateCheckResult createCheckResult = ex.getResult();
            facesError(createCheckResult.getMsg());
        }

        //  Возможно, тут есть проблемы с транзакционностью
        for (OrderItem orderItem : orderItems) {
            try {
                orderItem.setOrderId(order.getId());
                gf().saveOrderItem(orderItem);
            } catch (ValidationException ex) {
                logger.error(ex);
                ex.printStackTrace();
                ValidationResult validationResult = ex.getResult();
                for (ValidationError error : validationResult.getErrors()) {
                    facesError(error.toHumanString());
                }
            } catch (CreateFailureException ex) {
                logger.error(ex);
                ex.printStackTrace();
                CreateCheckResult createCheckResult = ex.getResult();
                facesError(createCheckResult.getMsg());
            }
        }

        Long loadedOrderId = order.getId();
        getSessionBean().setSelectedOrderId(loadedOrderId);

        this.orderInfo = null;
        this.file = null;
    }

    public static class OrderInfo {
        private final Order order;
        private final List<OrderItem> orderItems;

        public OrderInfo(Order order, List<OrderItem> orderItems) {
            if (order == null) {
                throw new IllegalArgumentException();
            }
            if (orderItems == null || orderItems.size() == 0) {
                throw new IllegalArgumentException();
            }

            this.order = order;
            this.orderItems = orderItems;
        }

        public Order getOrder() {
            return order;
        }

        public List<OrderItem> getOrderItems() {
            return orderItems;
        }
    }

    public OrderInfo getOrderInfo() {
        return orderInfo;
    }
}
